package arkham.net;

import java.io.*;
import java.net.*;

import arkham.net.messages.CommandMessage;
import arkham.net.messages.FileMessage;
import arkham.net.messages.InfoMessage;
import arkham.net.messages.Message;
import arkham.net.messages.TextMessage;
import arkham.net.messages.Message.MessageType;
import arkham.util.AHLogger;

public class ClientThread implements Runnable 
{
	private ServerBase server;
	private Socket clientSocket;
	private boolean running;
	private String username;
	private Object lock;
	
	public ClientThread(Socket client, ServerBase server)
	{
		this.clientSocket = client;
		this.server = server;
		running = false;
		lock = new Object();
		username = "<Unassigned>";
	}
	
	public void run() 
	{
		if (!clientSocket.isConnected())
		{
			AHLogger.warning("ClientThread", "Lost connection to client, terminating...");
			return;
		}
		try
		{
			// receive client's initial message
			Message msg = Message.receive(clientSocket);
			
			if (msg == null || !(msg instanceof TextMessage))
			{
				throw new IOException("Invalid message received from client.");
			}
			AHLogger.info("ClientThread", "Username received.");
			TextMessage tmsg = (TextMessage)msg;
			String proposedUsername = tmsg.getText();
			String givenName = server.registerClient(this, proposedUsername);
			this.username = givenName;
			// send back assigned username
			sendTextMessage(username);
			
			// start looping and receiving input
			startClient();
			while (isRunning() && clientSocket.isConnected()) 
			{
				msg = Message.receive(clientSocket);
				if (msg == null)
				{
					break;
				}
				else
				{
					messageReceived(msg);
				}	
			}
			
		}
		catch (IOException e)
		{
			AHLogger.warning("ClientThread", e.toString());
		}
		finally
		{
			try
			{
				server.removeClient(username);
				clientSocket.close();
				stopClient();
				AHLogger.info("Closing connection for client: " + username);
			}
			catch (IOException e)
			{
				AHLogger.warning("ClientThread", "Error cleaning up connection for: " + username);
			}
		}
	}
	
	/**
	 * @return true if this client is running
	 */
	public synchronized boolean isRunning() {
		return running;
	}

	/**
	 * Starts the client
	 */
	public synchronized void startClient()
	{
		running = true;
	}

	/**
	 * Stops the client
	 */
	public synchronized void stopClient()
	{
		running = false;
	}
	
	/**
	 * Gets the client's socket
	 * @return the socket
	 */
	public Socket getSocket()
	{
		return clientSocket;
	}
	
	/**
	 * Gets the username of this client
	 * @return the username
	 */
	public String getUsername()
	{
		return username;
	}
	
	/**
	 * This is the handler method which handles received messages
	 * from the client
	 * @param msg the message received
	 */
	public void messageReceived(Message msg)
	{
		// we will ignore all non-text messages for now
		if (msg.getType().equals(MessageType.TEXT_MESSAGE))
		{
			/* Text messages */
			server.textMessageReceived((TextMessage)msg, username);
		}
		else
		{
			server.messageReceived(msg, username);
		}
		
	}
	
	/**
	 * Writes a text message to the client
	 * @param text The message to send
	 */
	public void sendTextMessage(String text) 
	{
		synchronized(lock)
		{
			TextMessage tmsg = new TextMessage(text);
			boolean result = tmsg.send(clientSocket);
			if (!result)
			{
				stopClient();
			}
		}
	}
	
	/**
	 * Writes an info message to the client
	 * @param request the query
	 * @param response the response
	 * @throws IOException 
	 */
	public void sendInfoMessage(String request, String response)
	{
		synchronized (lock)
		{
			InfoMessage imsg = new InfoMessage(request, response);
			boolean result = imsg.send(clientSocket);
			if (!result)
			{
				stopClient();
			}
		}
	}
	
	/**
	 * Sends a file message to the client
	 * @param filename the file name
	 * @param filedata the file contents
	 */
	public void sendFileMessage(String filename, String filedata)
	{
		synchronized (lock)
		{
			FileMessage fmsg = new FileMessage(filename, filedata);
			boolean result = fmsg.send(clientSocket);
			if (!result)
			{
				stopClient();
			}
		}
	}
	
	/**
	 * Sends a command message to the client
	 * @param cmsg the command to send
	 */
	public void sendCommandMessage(CommandMessage cmsg)
	{
		synchronized(lock)
		{
			boolean result = cmsg.send(clientSocket);
			if (!result)
			{
				stopClient();
			}
		}
	}
}
